package svg;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URI;

import javax.imageio.ImageIO;

import com.kitfox.svg.Group;
import com.kitfox.svg.Line;
import com.kitfox.svg.Polygon;
import com.kitfox.svg.Rect;
import com.kitfox.svg.SVGCache;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGElementException;
import com.kitfox.svg.SVGException;
import com.kitfox.svg.ShapeElement;
import com.kitfox.svg.Text;
import com.kitfox.svg.animation.AnimationElement;

public class JobViewer 
{
	public static final int ARROW_UP = 0;
	public static final int ARROW_DOWN = 1;
	public static final int ARROW_RIGHT = 2;
	
	private int width;
	private int height;
	
	public JobViewer(int width, int height)
	{	
		this.width = width;
    	this.height = height;
	}
	
	public void generateImage(String title, String filename, GObject panel, 
			BufferedImage image, Graphics2D graphics)
	{
		StringReader reader = new StringReader(initSVG(width, height, title));
        URI uri = SVGCache.getSVGUniverse().loadSVG(reader, "myImage");
		
        panel.show(uri);
        
        SVGDiagram diagram = SVGCache.getSVGUniverse().getDiagram(uri);

        diagram.setIgnoringClipHeuristic(true);
        try 
        {
			diagram.render(graphics);
			ImageIO.write(image, "PNG", new File(filename));
		} 
        catch (Exception e) 
		{
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args)
	{
		// create JobViewer
		JobViewer jobViewer = new JobViewer(600, 600);
		
		// create BufferedImage and Graphics controller
		BufferedImage image = new BufferedImage(jobViewer.getWidth(), 
				jobViewer.getHeight(), BufferedImage.TYPE_INT_ARGB);
		Graphics2D graphics = image.createGraphics();
    	setGraphicsQuality(graphics);
    	graphics.setColor(Color.WHITE);
    	graphics.fillRect(0, 0, jobViewer.getWidth(), jobViewer.getHeight());
		
    	// create main panel
		Container mainPanel = new Container(10, 40, 
				jobViewer.getWidth() - 10 - 10, 
				jobViewer.getHeight() - 40 - 20);
        
        String name = "Task ";
        
        int numberOfTasks = 5;
        
        // create tasks and jobs
        for(int i = 0; i < numberOfTasks; i++)
        {
        	TaskCanvas taskPanel = new TaskCanvas(name + (i + 1), graphics, 60, 15, 10, 15);
        	
        	JobCanvas job = new JobCanvas(7 * i, (int)(Math.random() * 10.0), taskPanel);
        	taskPanel.getJobs().add(job);
        	
        	mainPanel.getElements().add(taskPanel);
        }
        
        {
        	TaskCanvas taskPanel = new TaskCanvas(name + "22", graphics, 60, 15, 10, 15);
        	
        	JobCanvas job = new JobCanvas(10, (int)(Math.random() * 10.0), taskPanel);
        	taskPanel.getJobs().add(job);
        	
        	mainPanel.getElements().add(taskPanel);
        }
        
        // layout everything
		mainPanel.layout(5);
		
		// make image
		jobViewer.generateImage("My Fancy Embedded Systems", "test.png", 
				mainPanel, image, graphics);
	}
    
    public static void addArrowedLine(URI uri, 
    		Point start, Point end, int arrowType, int arrowLength,
    		boolean fill, Color fillColor, 
    		boolean stroke, Color strokeColor, int strokeWidth)
    {
        SVGDiagram diagram = SVGCache.getSVGUniverse().getDiagram(uri); 
        Group group = (Group)diagram.getElement("elements");
        
        Line line = new Line();
        try
        {
            line.addAttribute("x1", AnimationElement.AT_XML, "" + start.getX());
            line.addAttribute("y1", AnimationElement.AT_XML, "" + start.getY());
        
            if(arrowType == ARROW_UP)
            {
            	line.addAttribute("x2", AnimationElement.AT_XML, "" + end.getX());
                line.addAttribute("y2", AnimationElement.AT_XML, "" + String.valueOf(end.getY() + arrowLength));
                
                Point p1 = new Point((int)end.getX() - arrowLength / 2, (int)end.getY() + arrowLength);
        		Point p2 = new Point((int)end.getX() + arrowLength / 2, (int)end.getY() + arrowLength);
        		Point p3 = new Point((int)end.getX()				  , (int)end.getY()); 
        		Point[] points = {p1, p2, p3};
        		
        		JobViewer.addPolygon(uri, points, true, fillColor, true, strokeColor, 1);
            }
            else if(arrowType == ARROW_DOWN)
            {
            	line.addAttribute("x2", AnimationElement.AT_XML, "" + end.getX());
                line.addAttribute("y2", AnimationElement.AT_XML, "" + String.valueOf(end.getY() - arrowLength));
                
                Point p1 = new Point((int)end.getX() - arrowLength / 2, (int)end.getY() - arrowLength);
        		Point p2 = new Point((int)end.getX() + arrowLength / 2, (int)end.getY() - arrowLength);
        		Point p3 = new Point((int)end.getX()				  , (int)end.getY()); 
        		Point[] points = {p1, p2, p3};
        		
        		JobViewer.addPolygon(uri, points, true, fillColor, true, strokeColor, 1);
            }
            else if(arrowType == ARROW_RIGHT)
            {
            	line.addAttribute("x2", AnimationElement.AT_XML, "" + String.valueOf(end.getX() - arrowLength));
                line.addAttribute("y2", AnimationElement.AT_XML, "" + end.getY());
                
                Point p1 = new Point((int)end.getX() - arrowLength, (int)end.getY() - arrowLength / 2);
        		Point p2 = new Point((int)end.getX() - arrowLength, (int)end.getY() + arrowLength / 2);
        		Point p3 = new Point((int)end.getX()				  , (int)end.getY()); 
        		Point[] points = {p1, p2, p3};
        		
        		JobViewer.addPolygon(uri, points, true, fillColor, true, strokeColor, 1);
            }
            else
            {
            	throw new RuntimeException("Don't know the arrow type!");
            }
            
            strokeAndShapeControl(line, fill, fillColor, stroke, strokeColor, strokeWidth);
            
            group.loaderAddChild(null, line);
            
            //Update animation state or group and it's decendants so that it reflects new animation values.
            // We could also call diagram.update(0.0) or SVGCache.getSVGUniverse().update().  Note that calling
            // circle.update(0.0) won't display anything since even though it will update the circle's state,
            // it won't update the parent group's state.
            group.updateTime(0.0);
        }
        catch (SVGException e)
        {
            e.printStackTrace();
        }
    }
    
    public static void addRectangle(URI uri, 
    		int x, int y, int width, int height,
    		boolean fill, Color fillColor, 
    		boolean stroke, Color strokeColor, int strokeWidth)
    {
        SVGDiagram diagram = SVGCache.getSVGUniverse().getDiagram(uri); 
        Group group = (Group)diagram.getElement("elements");
        
        Rect rect = new Rect();
        try
        {
            rect.addAttribute("x", AnimationElement.AT_XML, "" + x);
            rect.addAttribute("y", AnimationElement.AT_XML, "" + y);
            rect.addAttribute("width", AnimationElement.AT_XML, "" + width);
            rect.addAttribute("height", AnimationElement.AT_XML, "" + height);
            
            strokeAndShapeControl(rect, fill, fillColor, stroke, strokeColor, strokeWidth);
            
            group.loaderAddChild(null, rect);
            
            //Update animation state or group and it's decendants so that it reflects new animation values.
            // We could also call diagram.update(0.0) or SVGCache.getSVGUniverse().update().  Note that calling
            // circle.update(0.0) won't display anything since even though it will update the circle's state,
            // it won't update the parent group's state.
            group.updateTime(0.0);
        }
        catch (SVGException e)
        {
            e.printStackTrace();
        }
    }
    
    public static void writeText(URI uri, String str, int x, int y, Font font, Color color)
    {
    	SVGDiagram diagram = SVGCache.getSVGUniverse().getDiagram(uri); 
        Group group = (Group)diagram.getElement("elements");
        
        Text text = new Text();
        try
        {            
            text.addAttribute("x", AnimationElement.AT_XML, "" + x);
            text.addAttribute("y", AnimationElement.AT_XML, "" + y);
            text.addAttribute("font", AnimationElement.AT_CSS, font.getName());
            text.addAttribute("font-size", AnimationElement.AT_CSS, String.valueOf(font.getSize()));
            if(font.isPlain())
            {
            	text.addAttribute("font-weight", AnimationElement.AT_CSS, "normal");
            }
            else
            {
            	throw new RuntimeException("Don't know font type!");
            }
            strokeAndShapeControl(text, true, color, false, null, 0);

            text.appendText(str);
            
            group.loaderAddChild(null, text);
            text.build();
            
            //Update animation state or group and it's decendants so that it reflects new animation values.
            // We could also call diagram.update(0.0) or SVGCache.getSVGUniverse().update().  Note that calling
            // circle.update(0.0) won't display anything since even though it will update the circle's state,
            // it won't update the parent group's state.
            group.updateTime(0.0);
        }
        catch (SVGException e)
        {
            e.printStackTrace();
        }
    }
    
    public static void addPolygon(URI uri, 
    		Point[] points,
    		boolean fill, Color fillColor, 
    		boolean stroke, Color strokeColor, int strokeWidth)
    {
        SVGDiagram diagram = SVGCache.getSVGUniverse().getDiagram(uri); 
        Group group = (Group)diagram.getElement("elements");
        
        Polygon shape = new Polygon();
        try
        {
        	StringBuffer pointsStr = new StringBuffer();
        	
        	for(Point2D p : points)
        	{
        		pointsStr.append(p.getX() + "," + p.getY() + " ");
        	}
        	
        	shape.addAttribute("points", AnimationElement.AT_XML, "" + pointsStr.toString());
            
            strokeAndShapeControl(shape, fill, fillColor, stroke, strokeColor, strokeWidth);
            
            group.loaderAddChild(null, shape);
            
            //Update animation state or group and it's decendants so that it reflects new animation values.
            // We could also call diagram.update(0.0) or SVGCache.getSVGUniverse().update().  Note that calling
            // circle.update(0.0) won't display anything since even though it will update the circle's state,
            // it won't update the parent group's state.
            group.updateTime(0.0);
        }
        catch (SVGException e)
        {
            e.printStackTrace();
        }
    }
    
    public static void setGraphicsQuality(Graphics2D g2D) 
	{
	    g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
	            RenderingHints.VALUE_ANTIALIAS_ON);
	    g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
	            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
	    g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
	            RenderingHints.VALUE_RENDER_QUALITY);
	}
    
    private static String initSVG(int width, int height, String title)
    {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        
        pw.println("<svg width=\"" + String.valueOf(width) + "\" height=\"" + String.valueOf(height) + "\">");
        pw.println("    <g id=\"elements\"/>");
        pw.println("    <text id=\"userTextParent\" x=\"10\" y=\"30\" style=\"font-size:25;stroke:none;fill:black\">");
        pw.println("        <tspan id=\"userText\">" + title + "</tspan>");
        pw.println("    </text>");
        pw.println("</svg>");
        
        pw.close();
        return sw.toString();
    }

    private static String colorToString(Color color)
    {
    	return Integer.toHexString(color.getRGB() & 0xffffff);
    }
    
    private static void strokeAndShapeControl(ShapeElement shape,
    		boolean fill, Color fillColor, 
    		boolean stroke, Color strokeColor, int strokeWidth) throws SVGElementException
    {
    	if(fill)
        {
    		shape.addAttribute("fill", AnimationElement.AT_CSS, "#" + colorToString(fillColor));	
        }
        if(stroke)
        {
        	shape.addAttribute("stroke-width", AnimationElement.AT_XML, "" + strokeWidth);
        	shape.addAttribute("stroke", AnimationElement.AT_CSS, "#" + colorToString(strokeColor));
        }
    }

	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}
}
